package com.wangjiacai.utils.converter;


import com.wangjiacai.utils.web.exception.CodeMessageException;
import com.wangjiacai.utils.utils.BeanUtil;
import org.springframework.beans.factory.InitializingBean;

import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.function.BiConsumer;
import java.util.function.Consumer;

public abstract class BasicConverter<S, T> implements Converter<S, T>, InitializingBean {

    public List<Method> preMethods;

    public List<Method> postMethods;

    @Override
    public final void afterPropertiesSet() throws Exception {
        preMethods = new ArrayList<>();
        postMethods = new ArrayList<>();
        Method[] methods = this.getClass().getDeclaredMethods();
        for (Method method : methods) {
            if (method.getName().equals("preProcess")) {
                preMethods.add(method);
            }
            if (method.getName().equals("postProcess")) {
                postMethods.add(method);
            }
        }
    }

    private Method findMethod(List<Method> methods, Object[] param) {
        for (Method method : methods) {
            Class[] classes = method.getParameterTypes();
            if (classes.length != param.length) continue;
            for (int i = 0; i < classes.length; i++) {
                if (classes[i] != param.getClass()) continue;
            }
            return method;
        }
        return null;
    }

    private void  pre(S s, T t, Object[] param) {
        if(param==null){
            preProcess(s, t);
            return;
        }
        try {
            Object[] newParam = new Object[param.length + 2];
            newParam[0] = s;
            newParam[1] = t;
            for (int i = 0; i < param.length; i++) {
                newParam[i + 2] = param[i];
            }
            Method method = findMethod(preMethods, newParam);
            method.invoke(this, newParam);
        } catch (Exception e) {
            throw new CodeMessageException(e.getMessage());
        }
    }

    private void post(T t, Object[] param) {
        if(param==null){
            postProcess(t);
            return;
        }
        try {
            Object[] newParam= new Object[param.length + 1];
            newParam[0] = t;
            for (int i = 0; i < param.length; i++) {
                newParam[i + 1] = param[i];
            }
            Method method = findMethod(postMethods, newParam);
            method.invoke(this, newParam);
        } catch (Exception e) {
            throw new CodeMessageException(e.getMessage());
        }
    }

    public void preProcess(S s, T t) {
        BeanUtil.copyProperties(s, t);
    }


    public void postProcess(T t) {

    }

    abstract public int getPropSize();

    abstract public Object getQueryParam(S s, int i);

    abstract public String getQueryValueIdProp(int i);

    abstract public String getQueryValueProp(int i);

    abstract public void setPropertyValue(T t, int i, Object value);

    abstract public T newInstance();

    abstract public List  queryList(int i, Object param,Object[] preMethodParam,Object[] postMethodParam);

    abstract public Object query(int i, Object param,Object[] preMethodParam,Object[] postMethodParam);

    private T converterTo(S s,Object[] preMethodParam,Consumer<T> consumer) {
        if(s==null) return null;
        T t = newInstance();
        pre(s, t, preMethodParam);
        consumer.accept(t);
        return t;
    }

    private T converterTo(S s, int propSize, Executor executor, Object[] preMethodParam, Object[] postMethodParam) {
        T value=converterTo(s,preMethodParam,(t)->{
            CompletableFuture[] futureList = new CompletableFuture[propSize];
            for (int i = 0; i < propSize; i++) {
                futureList[i] = supplyAsync(t, getQueryParam(s, i), getQueryValueProp(i),i, executor, preMethodParam,postMethodParam);
            }
            CompletableFuture.allOf(futureList).join();
        });
        post(value,postMethodParam);
        return value;
    }

    private T converterToSync(S s, int propSize,Object[] preMethodParam, Object[] postMethodParam) {
        T value=converterTo(s,preMethodParam,(t)->{
            for (int i = 0; i < propSize; i++) {
                supplySync(t, getQueryParam(s, i), getQueryValueProp(i),i, preMethodParam,postMethodParam);
            }
        });
        post(value,postMethodParam);
        return value;
    }

    private List<T> converterToList(List<S> sList, int propSize,Object[] preMethodParam, Object[] postMethodParam, BiConsumer<List<Map<Object, List<T>>>,List<Set<Object>>> consumer) {
        List<T> tList =new ArrayList<>();
        if(sList==null||sList.size()==0) return tList;
        List<Set<Object>> queryParams = getQueryMap(propSize);
        List<Map<Object, List<T>>> observers = getObservers(propSize);

        sList.stream().forEach(s -> {
            T t = newInstance();
            pre(s, t, preMethodParam);
            tList.add(t);
            for (int i = 0; i < propSize; i++) {
                Object id = getQueryParam(s, i);
                if(id==null) continue;
                queryParams.get(i).add(id);
                List<T> list = observers.get(i).get(id);
                if (list == null) list = new ArrayList<>();
                list.add(t);
                observers.get(i).put(id, list);
            }
        });
        consumer.accept(observers,queryParams);
        for(T t:tList){
            post(t,postMethodParam);
        }
        return tList;
    }

    private List<T> converterToList(List<S> sList, int propSize, Executor executor, Object[] preMethodParam, Object[] postMethodParam) {
        List<T> tList=converterToList(sList,propSize,preMethodParam,postMethodParam,(observers,queryParams)->{
            CompletableFuture[] futureList = new CompletableFuture[propSize];
            for (int i = 0; i < propSize; i++) {
                futureList[i] = supplyAsync(observers.get(i), getQueryValueIdProp(i), queryParams.get(i), getQueryValueProp(i), i, executor,preMethodParam,postMethodParam);
            }
            CompletableFuture.allOf(futureList).join();
        });
        return tList;
    }

    private List<T> converterToListSync(List<S> sList, int propSize,Object[] preMethodParam, Object[] postMethodParam) {
        List<T> tList=converterToList(sList,propSize,preMethodParam,postMethodParam,(observers,queryParams)->{
            for (int i = 0; i < propSize; i++) {
                supplyAsync(observers.get(i), getQueryValueIdProp(i), queryParams.get(i), getQueryValueProp(i), i,preMethodParam,postMethodParam);
            }
        });
        return tList;
    }

    private void processResponseList(List<Object> responseList,Map<Object, List<T>> observer, String queryValueIdProp,String queryValueProp, int i){
        if (responseList != null) {
            responseList.stream().forEach(response -> {
                Object id = BeanUtil.getPropertyValue(response, queryValueIdProp,Object.class);
                if(queryValueProp==null) observer.get(id).stream().forEach(t -> setPropertyValue(t, i, response));
                else{
                    Object value = BeanUtil.getPropertyValue(response, queryValueProp,Object.class);
                    observer.get(id).stream().forEach(t -> setPropertyValue(t, i, value));
                }
            });
        }
    }

    private CompletableFuture supplyAsync(Map<Object, List<T>> observer, String queryValueIdProp, Object queryParam, String queryValueProp, int i, Executor executor,Object[] preMethodParam,Object[] postMethodParam) {
        return CompletableFuture.supplyAsync(() -> queryList(i, queryParam,preMethodParam,postMethodParam), executor)
                .thenAccept(responseList -> processResponseList(responseList,observer,queryValueIdProp,queryValueProp,i));
    }

    private void supplyAsync(Map<Object, List<T>> observer, String queryValueIdProp, Object queryParam, String queryValueProp, int i,Object[] preMethodParam,Object[] postMethodParam) {
        List<Object> responseList=queryList(i, queryParam,preMethodParam,postMethodParam);
        processResponseList(responseList,observer,queryValueIdProp,queryValueProp,i);
    }


    private void processResponse(Object response,T t, String queryValueProp,int i){
        if(response==null) return;
        if(queryValueProp==null) setPropertyValue(t, i, response);
        else {
            Object value = BeanUtil.getPropertyValue(response, queryValueProp,Object.class);
            setPropertyValue(t, i, value);
        }
    }

    private CompletableFuture supplyAsync(T t, Object queryParam, String queryValueProp,int i, Executor executor,Object[] preMethodParam, Object[] postMethodParam) {
        return CompletableFuture.supplyAsync(() -> query(i, queryParam,preMethodParam,postMethodParam), executor)
                .thenAccept(response -> processResponse(response,t,queryValueProp,i));
    }

    private void supplySync(T t, Object queryParam, String queryValueProp,int i,Object[] preMethodParam, Object[] postMethodParam) {
        Object response=query(i, queryParam,preMethodParam,postMethodParam);
        processResponse(response,t,queryValueProp,i);
    }

    private List<Set<Object>> getQueryMap(int propSize) {
        List<Set<Object>> list = new ArrayList<>();
        for (int i = 0; i < propSize; i++) {
            list.add(Collections.synchronizedSet(new HashSet<>()));
        }
        return list;
    }

    private List<Map<Object, List<T>>> getObservers(int propSize) {
        List<Map<Object, List<T>>> observers = new ArrayList<>();
        for (int i = 0; i < propSize; i++) {
            observers.add(new ConcurrentHashMap<>());
        }
        return observers;
    }


    @Override
    public T converterTo(S s, Executor executor) {
        return converterTo(s, getPropSize(), executor, null, null);
    }

    @Override
    public T converterToSync(S s) {
        return converterToSync(s, getPropSize(),null, null);
    }

    public T converterToPreMethod(S s, Executor executor, Object[] preMethodParam) {
        return converterTo(s, getPropSize(), executor, preMethodParam, null);
    }

    public T converterToPreMethodSync(S s,  Object[] preMethodParam) {
        return converterToSync(s, getPropSize(), preMethodParam, null);
    }

    public T converterToPostMethod(S s, Executor executor, Object[] postMethodParam) {
        return converterTo(s, getPropSize(), executor, null, postMethodParam);
    }

    public T converterToPostMethodSync(S s,Object[] postMethodParam) {
        return converterToSync(s, getPropSize(), null, postMethodParam);
    }

    public T converterToRoundMethod(S s, Executor executor, Object[] preMethdoParam, Object[] postMethodParam) {
        return converterTo(s, getPropSize(), executor, preMethdoParam, postMethodParam);
    }

    public T converterToRoundMethodSync(S s,Object[] preMethodParam, Object[] postMethodParam) {
        return converterToSync(s, getPropSize(), preMethodParam, postMethodParam);
    }

    @Override
    public List<T> converterToList(List<S> sList, Executor executor) {
        return converterToList(sList, getPropSize(), executor, null, null);
    }

    @Override
    public List<T> converterToListSync(List<S> sList) {
        return converterToListSync(sList, getPropSize(), null, null);
    }

    public List<T> converterToListPostMethod(List<S> sList, Executor executor, Object[] postMethodParam) {
        return converterToList(sList, getPropSize(), executor, null, postMethodParam);
    }

    public List<T> converterToListPostMethodSync(List<S> sList,Object[] postMethodParam) {
        return converterToListSync(sList, getPropSize(), null, postMethodParam);
    }

    public List<T> converterToListPreMethod(List<S> sList, Executor executor, Object[] preMethodParam) {
        return converterToList(sList, getPropSize(), executor, preMethodParam, null);
    }

    public List<T> converterToListPreMethodSync(List<S> sList,Object[] postMethodParam) {
        return converterToListSync(sList, getPropSize(), postMethodParam, null);
    }

    public List<T> converterToListRoundMethod(List<S> sList, Executor executor, Object[] preMethodParam, Object[] postMethodParam) {
        return converterToList(sList, getPropSize(), executor, preMethodParam, postMethodParam);
    }

    public List<T> converterToListRoundMethodSync(List<S> sList,Object[] preMethodParam, Object[] postMethodParam) {
        return converterToListSync(sList, getPropSize(), preMethodParam, postMethodParam);
    }
}
